-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
비동기 처리 시 메서드를 직접 호출하는 코드를 이벤트 발행으로 변경 #871
Conversation
Walkthrough이 풀 리퀘스트는 톡픽(TalkPick) 관련 서비스의 아키텍처를 이벤트 기반으로 리팩토링하는 작업을 포함합니다. 주요 변경사항은 파일 처리, 요약 서비스, 이벤트 핸들링 로직의 재구조화입니다. 기존의 직접적인 의존성을 제거하고 Changes
Assessment against linked issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🔭 Outside diff range comments (1)
src/main/java/balancetalk/talkpick/application/TalkPickFileService.java (1)
비동기 메서드에서 매개변수 리스트의 안전한 복사본 생성 필요
handleFilesOnTalkPickUpdate
메서드가@Async
로 비동기 실행되므로, 매개변수로 전달된 리스트의 직접적인 수정은 동시성 문제를 일으킬 수 있습니다. 다음과 같이 수정하는 것이 안전합니다:public void handleFilesOnTalkPickUpdate(List<Long> newFileIds, List<Long> deleteFileIds, Long talkPickId) { deleteFiles(deleteFileIds); List<Long> safeNewFileIds = new ArrayList<>(newFileIds); safeNewFileIds.removeIf(deleteFileIds::contains); relocateFiles(safeNewFileIds, talkPickId); }🔗 Analysis chain
Line range hint
41-45
: 업데이트 메서드의 동시성 이슈 가능성 검토 필요
newFileIds.removeIf((deleteFileIds::contains))
연산이 동시성 문제를 일으킬 수 있습니다. 리스트 수정 전에 복사본을 만드는 것이 안전할 수 있습니다.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # 동시성 관련 이슈가 있는지 코드베이스 검사 rg -l "handleFilesOnTalkPickUpdate.*@Async" | xargs rg "removeIf|remove|clear|add" -A 5 -B 5Length of output: 44611
🧹 Nitpick comments (7)
src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepository.java (1)
14-14
: 비동기 처리를 위한 메서드 시그니처가 적절합니다.
findAllBySummaryStatus
메서드는:
- Spring Data JPA의 명명 규칙을 잘 따르고 있습니다
- 전체 엔티티를 반환하여 N+1 문제를 방지할 수 있습니다
- 이벤트 기반 처리를 위한 데이터 조회에 적합합니다
하나 제안드리고 싶은 점은, 대량의 데이터 처리가 예상된다면 페이징 처리를 고려해보시는 것도 좋을 것 같습니다.
src/main/java/balancetalk/talkpick/application/TalkPickScheduleService.java (1)
16-16
: 실패한 요약 재시도 로직이 효과적으로 개선되었습니다.기존의 복잡한 구현이 단일 서비스 메서드 호출로 단순화되어 다음과 같은 이점이 있습니다:
- 코드의 가독성 향상
- 관심사의 명확한 분리
- 불필요한 비동기 처리 제거로 리소스 사용 최적화
src/main/java/balancetalk/talkpick/application/TalkPickService.java (1)
57-58
: 파일 조회 시 데이터베이스 호출 최적화 제안
fileRepository
를 사용하여 이미지 URL과 파일 ID를 각각 조회하고 있는데, 이는 두 번의 데이터베이스 호출을 발생시킬 수 있습니다. 한 번의 쿼리로 필요한 정보를 모두 가져올 수 있도록 리포지토리 메서드를 개선하는 것을 고려해보시기 바랍니다.src/main/java/balancetalk/talkpick/domain/event/TalkPickEventHandler.java (1)
22-27
: 업데이트 이벤트에서 불필요한 요약 작업 검토 필요파일만 변경되고 내용이 변경되지 않은 경우에도 요약 작업이 수행됩니다. 내용이 변경된 경우에만 요약 작업을 수행하도록 최적화가 필요합니다.
src/main/java/balancetalk/talkpick/application/TalkPickFileService.java (1)
Line range hint
55-65
: 존재 여부 확인 로직 최적화 필요
notExistsFilesBy
메서드를 호출한 후 다시 파일을 조회하는 것은 비효율적일 수 있습니다. 한 번의 쿼리로 처리하는 것이 좋겠습니다.@Async @Retryable(backoff = @Backoff(delay = 1000)) @Transactional public void handleFilesOnTalkPickDelete(Long talkPickId) { - if (notExistsFilesBy(talkPickId)) { - return; - } List<File> files = fileRepository.findAllByResourceIdAndFileType(talkPickId, TALK_PICK); + if (files.isEmpty()) { + return; + } fileHandler.deleteFiles(files); } - - private boolean notExistsFilesBy(Long talkPickId) { - return !fileRepository.existsByResourceIdAndFileType(talkPickId, TALK_PICK); - }src/test/java/balancetalk/talkpick/application/TalkPickServiceTest.java (2)
71-73
: 테스트 데이터 설정이 개선되어야 합니다.빈 리스트 대신 실제와 유사한 테스트 데이터를 사용하여 테스트하는 것이 좋겠습니다.
- when(fileRepository.findImgUrlsByResourceIdAndFileType(1L, TALK_PICK)).thenReturn(List.of()); - when(fileRepository.findIdsByResourceIdAndFileType(1L, TALK_PICK)).thenReturn(List.of()); + when(fileRepository.findImgUrlsByResourceIdAndFileType(1L, TALK_PICK)) + .thenReturn(List.of("image1.jpg", "image2.jpg")); + when(fileRepository.findIdsByResourceIdAndFileType(1L, TALK_PICK)) + .thenReturn(List.of(1L, 2L));
Line range hint
87-105
: 테스트 케이스 중복 코드 제거 필요여러 테스트에서 반복되는 given 부분을 @beforeeach로 이동하여 코드 중복을 제거하는 것이 좋겠습니다.
@BeforeEach void setUp() { // ... 기존 코드 ... + when(talkPickRepository.findById(1L)).thenReturn(Optional.ofNullable(talkPick)); + when(guestOrApiMember.isGuest()).thenReturn(true); + when(fileRepository.findImgUrlsByResourceIdAndFileType(1L, TALK_PICK)) + .thenReturn(List.of("image1.jpg")); + when(fileRepository.findIdsByResourceIdAndFileType(1L, TALK_PICK)) + .thenReturn(List.of(1L)); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
src/main/java/balancetalk/talkpick/application/TalkPickFileService.java
(3 hunks)src/main/java/balancetalk/talkpick/application/TalkPickScheduleService.java
(1 hunks)src/main/java/balancetalk/talkpick/application/TalkPickService.java
(4 hunks)src/main/java/balancetalk/talkpick/application/TalkPickSummaryService.java
(1 hunks)src/main/java/balancetalk/talkpick/domain/event/TalkPickCreatedEvent.java
(1 hunks)src/main/java/balancetalk/talkpick/domain/event/TalkPickDeletedEvent.java
(1 hunks)src/main/java/balancetalk/talkpick/domain/event/TalkPickEventHandler.java
(1 hunks)src/main/java/balancetalk/talkpick/domain/event/TalkPickUpdatedEvent.java
(1 hunks)src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepository.java
(1 hunks)src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepositoryCustom.java
(0 hunks)src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepositoryImpl.java
(0 hunks)src/test/java/balancetalk/talkpick/application/TalkPickServiceTest.java
(5 hunks)
💤 Files with no reviewable changes (2)
- src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepositoryImpl.java
- src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepositoryCustom.java
✅ Files skipped from review due to trivial changes (1)
- src/main/java/balancetalk/talkpick/domain/event/TalkPickDeletedEvent.java
🔇 Additional comments (12)
src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepository.java (1)
3-14
: 리포지토리 인터페이스가 잘 개선되었습니다!기존의 커스텀 구현에서 Spring Data JPA의 규약 기반 쿼리 메서드로 변경한 것이 좋은 선택입니다. 이는 코드를 더 간단하고 유지보수하기 쉽게 만들었습니다.
src/main/java/balancetalk/talkpick/application/TalkPickScheduleService.java (1)
11-11
: 의존성 주입 필드명이 역할을 더 잘 표현하도록 개선되었습니다.
TalkPickSummarizer
에서TalkPickSummaryService
로의 변경은 해당 컴포넌트의 서비스 계층으로서의 역할을 더 명확하게 표현합니다.src/main/java/balancetalk/talkpick/application/TalkPickService.java (4)
37-38
: 이벤트 퍼블리셔와 파일 리포지토리 의존성 주입 추가 확인
ApplicationEventPublisher
와FileRepository
를 의존성으로 추가하여 이벤트 기반 아키텍처로의 전환을 잘 반영하였습니다.
46-46
: TalkPick 생성 시 이벤트 발행으로 비동기 처리 개선
createTalkPick
메서드에서eventPublisher
를 통해TalkPickCreatedEvent
를 발행하여 파일 처리와 요약 생성 작업을 트랜잭션 후에 비동기로 처리하도록 리팩토링한 점이 적절합니다.
89-90
: TalkPick 업데이트 시 이벤트 발행으로 처리 흐름 개선
updateTalkPick
메서드에서TalkPickUpdatedEvent
를 발행하여 업데이트 관련 비동기 처리를 효율적으로 관리한 점이 좋습니다.
99-99
: TalkPick 삭제 시 이벤트 발행으로 비동기 처리 구현
deleteTalkPick
메서드에서TalkPickDeletedEvent
를 발행하여 삭제 관련 후속 작업을 트랜잭션 후에 비동기로 처리하도록 한 점이 적절합니다.src/main/java/balancetalk/talkpick/domain/event/TalkPickCreatedEvent.java (1)
1-15
: TalkPickCreatedEvent 클래스 정의 확인
TalkPickCreatedEvent
클래스가 생성되어 TalkPick 생성 시 필요한 이벤트 정보를 잘 캡슐화하고 있습니다. Lombok 어노테이션을 활용하여 보일러플레이트 코드를 줄인 점이 좋습니다.src/main/java/balancetalk/talkpick/domain/event/TalkPickUpdatedEvent.java (1)
1-16
: TalkPickUpdatedEvent 클래스 정의 확인
TalkPickUpdatedEvent
클래스가 추가되어 TalkPick 업데이트 시 새로운 파일과 삭제할 파일 정보를 효과적으로 전달할 수 있게 되었습니다. 이벤트 기반 아키텍처로의 전환을 잘 반영하였습니다.src/main/java/balancetalk/talkpick/domain/event/TalkPickEventHandler.java (2)
9-15
: 이벤트 핸들러의 의존성 주입 및 구조가 잘 설계되었습니다.클래스가 단일 책임 원칙을 잘 따르고 있으며, 필요한 서비스들이 적절히 주입되어 있습니다.
29-32
: 삭제 이벤트 처리가 적절합니다.파일 삭제 로직이 명확하고 간단하게 구현되어 있습니다.
src/main/java/balancetalk/talkpick/application/TalkPickFileService.java (1)
27-29
: null 체크 로직이 적절합니다.파일 ID 리스트에 대한 null과 empty 체크를 통해 불필요한 처리를 방지하고 있습니다.
src/main/java/balancetalk/talkpick/application/TalkPickSummaryService.java (1)
58-59
:@Async
와@Transactional
사용 시 트랜잭션 전파 확인 필요
@Async
를 사용하면 메서드가 별도의 스레드에서 실행되므로,@Transactional
이 의도한 대로 작동하지 않을 수 있습니다. 트랜잭션 전파에 문제가 없는지 확인하고, 필요한 경우@Transactional
애너테이션을summarize
메서드로 이동하는 것을 고려해 보세요.
src/main/java/balancetalk/talkpick/domain/event/TalkPickEventHandler.java
Show resolved
Hide resolved
src/main/java/balancetalk/talkpick/application/TalkPickSummaryService.java
Show resolved
Hide resolved
log.error("Fail to summary TalkPick ID = {}", talkPickId); | ||
log.error("exception message = {} {}", e.getMessage(), e.getStackTrace()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로그 메시지 및 예외 처리 개선 필요
로그 메시지의 문법 오류를 수정하고, 예외와 스택 트레이스를 올바르게 로깅하기 위해 log.error
메서드의 사용을 개선해야 합니다.
다음과 같이 수정하는 것을 제안합니다:
-log.error("Fail to summary TalkPick ID = {}", talkPickId);
-log.error("exception message = {} {}", e.getMessage(), e.getStackTrace());
+log.error("Failed to summarize TalkPick ID = {}", talkPickId, e);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
log.error("Fail to summary TalkPick ID = {}", talkPickId); | |
log.error("exception message = {} {}", e.getMessage(), e.getStackTrace()); | |
log.error("Failed to summarize TalkPick ID = {}", talkPickId, e); |
Quality Gate passedIssues Measures |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/test/java/balancetalk/talkpick/application/TalkPickServiceTest.java (1)
71-72
: 테스트 메서드 변경이 일관성 있게 이루어졌습니다!파일 저장소 메서드 호출로의 변경이 모든 테스트 케이스에 올바르게 적용되었습니다.
테스트 유지보수성 개선을 위한 제안
여러 테스트 메서드에서 동일한 mock 설정이 반복되고 있습니다. 이를
@BeforeEach
에서 처리하는 것이 좋을 것 같습니다.다음과 같이 수정하는 것을 고려해보세요:
@BeforeEach void setUp() { talkPick = mock(TalkPick.class); member = mock(Member.class); guestOrApiMember = mock(GuestOrApiMember.class); when(talkPick.getId()).thenReturn(1L); // ... existing setup ... + when(fileRepository.findImgUrlsByResourceIdAndFileType(1L, TALK_PICK)).thenReturn(List.of()); + when(fileRepository.findIdsByResourceIdAndFileType(1L, TALK_PICK)).thenReturn(List.of()); }Also applies to: 87-88, 103-104
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/test/java/balancetalk/talkpick/application/TalkPickServiceTest.java
(5 hunks)
🔇 Additional comments (2)
src/test/java/balancetalk/talkpick/application/TalkPickServiceTest.java (2)
3-3
: 적절한 임포트 변경이 이루어졌습니다!파일 처리 로직의 아키텍처 변경에 따른 임포트 수정이 올바르게 반영되었습니다.
Also applies to: 11-11
39-39
: Mock 객체 변경이 적절합니다!
TalkPickFileHandler
에서FileRepository
로의 의존성 변경이 테스트에 올바르게 반영되었습니다.
💡 작업 내용
💡 자세한 설명
#870 해당 이슈의 원인을 다음과 같이 분석했습니다.
@Async
가 붙은 summarizeTalkPick 메서드는 이를 호출하는 메인 스레드의 작업, 즉 톡픽 생성 메서드와 다른 트랜잭션 주기를 가짐create tx
톡픽 저장summarize tx
톡픽 조회 → 에러 발생create tx
커밋이러한 이유로, 비동기 작업의 실행 시점을 트랜잭션 커밋 이후로 보장하기 위해
@TransactionalEventListener
를 사용한 이벤트 기반으로 리팩토링했습니다.직접 호출 방식에서 이벤트 발행으로 변경
메인 메서드에서 비동기 작업을 직접 호출하는 대신, 이벤트를 발행하는 방식으로 변경했습니다.
이를 통해 트랜잭션 커밋 전에 비동기 작업이 실행되어 발생하는 오류를 해결했습니다.
요약에 실패한 톡픽에 대한 재요약 스케줄링 코드 개선
기존 코드에서 스케줄링 메서드 실행 시 비동기 스레드를 사용했습니다.
하지만 스케줄링 작업은 단일 스레드에서 실행해도 충분하며, 별도의 비동기 스레드를 생성하여 실행하면 시스템 리소스를 불필요하게 소비하게 될 것이라 판단했습니다.
따라서 비동기 메서드를 실행하는게 아니라, 단일 스레드에서 실행되도록 처리했습니다.
✅ 셀프 체크리스트
closes #870
Summary by CodeRabbit
새 기능
버그 수정
리팩토링